// Copyright (c) 2004-2013 Sergey Lyubka
// Copyright (c) 2013-2022 Cesanta Software Limited
// All rights reserved
//
// This software is dual-licensed: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation. For the terms of this
// license, see http://www.gnu.org/licenses/
//
// You are free to use this software under the terms of the GNU General
// Public License, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// Alternatively, you can license this software under a commercial
// license, as set out in https://www.mongoose.ws/licensing/
//
// SPDX-License-Identifier: GPL-2.0-only or commercial
 
#ifndef MONGOOSE_H
#define MONGOOSE_H

#define MG_VERSION "7.9"

#ifdef __cplusplus
extern "C"
{
#endif

#define MG_ARCH_CUSTOM 0    // User creates its own mongoose_custom.h
#define MG_ARCH_UNIX 1      // Linux, BSD, Mac, ...
#define MG_ARCH_WIN32 2     // Windows
#define MG_ARCH_ESP32 3     // ESP32
#define MG_ARCH_ESP8266 4   // ESP8266
#define MG_ARCH_FREERTOS 5  // FreeRTOS
#define MG_ARCH_AZURERTOS 6 // MS Azure RTOS
#define MG_ARCH_ZEPHYR 7    // Zephyr RTOS
#define MG_ARCH_NEWLIB 8    // Bare metal ARM
#define MG_ARCH_RTX 9       // Keil MDK RTX
#define MG_ARCH_TIRTOS 10   // Texas Semi TI-RTOS
#define MG_ARCH_RP2040 11   // Raspberry Pi RP2040

#if !defined(MG_ARCH)
#if defined(__unix__) || defined(__APPLE__)
#define MG_ARCH MG_ARCH_UNIX
#elif defined(_WIN32)
#define MG_ARCH MG_ARCH_WIN32
#elif defined(ICACHE_FLASH) || defined(ICACHE_RAM_ATTR)
#define MG_ARCH MG_ARCH_ESP8266
#elif defined(__ZEPHYR__)
#define MG_ARCH MG_ARCH_ZEPHYR
#elif defined(ESP_PLATFORM)
#define MG_ARCH MG_ARCH_ESP32
#elif defined(FREERTOS_IP_H)
#define MG_ARCH MG_ARCH_FREERTOS
#define MG_ENABLE_FREERTOS_TCP 1
#elif defined(AZURE_RTOS_THREADX)
#define MG_ARCH MG_ARCH_AZURERTOS
#elif defined(PICO_TARGET_NAME)
#define MG_ARCH MG_ARCH_RP2040
#endif
#endif // !defined(MG_ARCH)

#if !defined(MG_ARCH) || (MG_ARCH == MG_ARCH_CUSTOM)
#include "mongoose_custom.h" // keep this include
#endif

#if !defined(MG_ARCH)
#error "MG_ARCH is not specified and we couldn't guess it. Set -D MG_ARCH=..."
#endif

// http://esr.ibiblio.org/?p=5095
#define MG_BIG_ENDIAN (*(uint16_t *)"\0\xff" < 0x100)

#if MG_ARCH == MG_ARCH_AZURERTOS

#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <time.h>

#include <fx_api.h>
#include <tx_api.h>

#include <nx_api.h>
#include <nx_bsd.h>
#include <nx_port.h>
#include <tx_port.h>

#define PATH_MAX FX_MAXIMUM_PATH
#define MG_DIRSEP '\\'

#define socklen_t int
#define closesocket(x) soc_close(x)

#undef FOPEN_MAX

#endif

#if MG_ARCH == MG_ARCH_ESP32

#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <netdb.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>

#include <esp_timer.h>

#define MG_PATH_MAX 128

#endif

#if MG_ARCH == MG_ARCH_ESP8266

#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <netdb.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>

#include <esp_system.h>

#define MG_PATH_MAX 128

#endif

#if MG_ARCH == MG_ARCH_FREERTOS

#include <ctype.h>
// #include <errno.h> // Cannot include errno - might conflict with lwip!
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h> // rand(), strtol(), atoi()
#include <string.h>
#include <sys/stat.h>

#include <FreeRTOS.h>
#include <task.h>

#ifndef MG_IO_SIZE
#define MG_IO_SIZE 512
#endif

#define calloc(a, b) mg_calloc(a, b)
#define free(a) vPortFree(a)
#define malloc(a) pvPortMalloc(a)
#define strdup(s) ((char *)mg_strdup(mg_str(s)).ptr)

    // Re-route calloc/free to the FreeRTOS's functions, don't use stdlib
    static inline void *mg_calloc(size_t cnt, size_t size)
    {
        void *p = pvPortMalloc(cnt * size);
        if (p != NULL)
            memset(p, 0, size * cnt);
        return p;
    }

#define mkdir(a, b) mg_mkdir(a, b)
    static inline int mg_mkdir(const char *path, mode_t mode)
    {
        (void)path, (void)mode;
        return -1;
    }

#endif // MG_ARCH == MG_ARCH_FREERTOS

#if MG_ARCH == MG_ARCH_NEWLIB
#define _POSIX_TIMERS

#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#define MG_PATH_MAX 100
#define MG_ENABLE_SOCKET 0
#define MG_ENABLE_DIRLIST 0

#endif

#if MG_ARCH == MG_ARCH_RP2040
#include <errno.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <pico/stdlib.h>
    int mkdir(const char *, mode_t);
#endif

#if MG_ARCH == MG_ARCH_RTX

#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#if !defined MG_ENABLE_RL && (!defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP)
#define MG_ENABLE_RL 1
#endif

#endif

#if MG_ARCH == MG_ARCH_TIRTOS

#include <stdlib.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#include <serrno.h>
#include <sys/socket.h>

#include <ti/sysbios/knl/Clock.h>

#endif

#if MG_ARCH == MG_ARCH_UNIX

#define _DARWIN_UNLIMITED_SELECT 1 // No limit on file descriptors

#if defined(__APPLE__)
#include <mach/mach_time.h>
#endif

#if !defined(MG_ENABLE_EPOLL) && defined(__linux__)
#define MG_ENABLE_EPOLL 1
#elif !defined(MG_ENABLE_POLL)
#define MG_ENABLE_POLL 1
#endif

#include <arpa/inet.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if defined(MG_ENABLE_EPOLL) && MG_ENABLE_EPOLL
#include <sys/epoll.h>
#elif defined(MG_ENABLE_POLL) && MG_ENABLE_POLL
#include <poll.h>
#else
#include <sys/select.h>
#endif
  
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#ifndef MG_ENABLE_DIRLIST
#define MG_ENABLE_DIRLIST 1
#endif

#ifndef MG_PATH_MAX
#define MG_PATH_MAX FILENAME_MAX
#endif

#endif

#if MG_ARCH == MG_ARCH_WIN32

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif

#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif

#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#endif

#include <ctype.h>
#include <direct.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <Windows.h>

typedef int bool;

#if defined(_MSC_VER) && _MSC_VER < 1700
#define __func__ ""
    typedef __int64 int64_t;
    typedef unsigned __int64 uint64_t;
    typedef unsigned char uint8_t;
    typedef char int8_t;
    typedef unsigned short uint16_t;
    typedef short int16_t;
    typedef unsigned int uint32_t;
    typedef int int32_t;
    typedef enum
    {
        false = 0,
        true = 1
    } bool;
#else
#include <stdbool.h>
#include <stdint.h>
#include <ws2tcpip.h>
#endif

#include <process.h>
#include <winerror.h>
#include <winsock2.h>

// Protect from calls like std::snprintf in app code
// See https://github.com/cesanta/mongoose/issues/1047
#ifndef __cplusplus
#define snprintf _snprintf
#define vsnprintf _vsnprintf
#ifndef strdup // For MSVC with _DEBUG, see #1359
#define strdup(x) _strdup(x)
#endif
#endif

#define MG_INVALID_SOCKET INVALID_SOCKET
#define MG_SOCKET_TYPE SOCKET
    typedef unsigned long nfds_t;
#if defined(_MSC_VER)
#pragma comment(lib, "ws2_32.lib")
#ifndef alloca
#define alloca(a) _alloca(a)
#endif
#endif
#define poll(a, b, c) WSAPoll((a), (b), (c))
#define closesocket(x) closesocket(x)

    typedef int socklen_t;
#define MG_DIRSEP '\\'

#ifndef MG_PATH_MAX
#define MG_PATH_MAX FILENAME_MAX
#endif

#ifndef SO_EXCLUSIVEADDRUSE
#define SO_EXCLUSIVEADDRUSE ((int)(~SO_REUSEADDR))
#endif

#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? WSAGetLastError() : 0)

#define MG_SOCK_PENDING(errcode)                                              \
    (((errcode) < 0) &&                                                       \
     (WSAGetLastError() == WSAEINTR || WSAGetLastError() == WSAEINPROGRESS || \
      WSAGetLastError() == WSAEWOULDBLOCK))

#define MG_SOCK_RESET(errcode) \
    (((errcode) < 0) && (WSAGetLastError() == WSAECONNRESET))

#define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX)
#define sleep(x) Sleep(x)
#define mkdir(a, b) _mkdir(a)

#ifndef S_ISDIR
#define S_ISDIR(x) (((x)&_S_IFMT) == _S_IFDIR)
#endif

#ifndef MG_ENABLE_DIRLIST
#define MG_ENABLE_DIRLIST 1
#endif

#endif

#if MG_ARCH == MG_ARCH_ZEPHYR

#include <zephyr/kernel.h>

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <zephyr/net/socket.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>

#define MG_PUTCHAR(x) printk("%c", x)
#ifndef strdup
#define strdup(s) ((char *)mg_strdup(mg_str(s)).ptr)
#endif
#define strerror(x) zsock_gai_strerror(x)
#define FD_CLOEXEC 0
#define F_SETFD 0
#define MG_ENABLE_SSI 0

    int rand(void);
    int sscanf(const char *, const char *, ...);

#endif

#if defined(MG_ENABLE_FREERTOS_TCP) && MG_ENABLE_FREERTOS_TCP

#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>

#include <FreeRTOS.h>
#include <list.h>
#include <task.h>

#include <FreeRTOS_IP.h>
#include <FreeRTOS_Sockets.h>

#define MG_SOCKET_TYPE Socket_t
#define MG_INVALID_SOCKET FREERTOS_INVALID_SOCKET

// Why FreeRTOS-TCP did not implement a clean BSD API, but its own thing
// with FreeRTOS_ prefix, is beyond me
#define IPPROTO_TCP FREERTOS_IPPROTO_TCP
#define IPPROTO_UDP FREERTOS_IPPROTO_UDP
#define AF_INET FREERTOS_AF_INET
#define SOCK_STREAM FREERTOS_SOCK_STREAM
#define SOCK_DGRAM FREERTOS_SOCK_DGRAM
#define SO_BROADCAST 0
#define SO_ERROR 0
#define SOL_SOCKET 0
#define SO_REUSEADDR 0
#define sockaddr_in freertos_sockaddr
#define sockaddr freertos_sockaddr
#define accept(a, b, c) FreeRTOS_accept((a), (b), (c))
#define connect(a, b, c) FreeRTOS_connect((a), (b), (c))
#define bind(a, b, c) FreeRTOS_bind((a), (b), (c))
#define listen(a, b) FreeRTOS_listen((a), (b))
#define socket(a, b, c) FreeRTOS_socket((a), (b), (c))
#define send(a, b, c, d) FreeRTOS_send((a), (b), (c), (d))
#define recv(a, b, c, d) FreeRTOS_recv((a), (b), (c), (d))
#define setsockopt(a, b, c, d, e) FreeRTOS_setsockopt((a), (b), (c), (d), (e))
#define sendto(a, b, c, d, e, f) FreeRTOS_sendto((a), (b), (c), (d), (e), (f))
#define recvfrom(a, b, c, d, e, f) \
    FreeRTOS_recvfrom((a), (b), (c), (d), (e), (f))
#define closesocket(x) FreeRTOS_closesocket(x)
#define gethostbyname(x) FreeRTOS_gethostbyname(x)
#define getsockname(a, b, c) mg_getsockname((a), (b), (c))
#define getpeername(a, b, c) mg_getpeername((a), (b), (c))

    static inline int mg_getsockname(MG_SOCKET_TYPE fd, void *buf, socklen_t *len)
    {
        (void)fd, (void)buf, (void)len;
        return -1;
    }

    static inline int mg_getpeername(MG_SOCKET_TYPE fd, void *buf, socklen_t *len)
    {
        (void)fd, (void)buf, (void)len;
        return 0;
    }
#endif

#if defined(MG_ENABLE_LWIP) && MG_ENABLE_LWIP
#if defined(__GNUC__)
#include <sys/stat.h>
#include <sys/time.h>
#else
    struct timeval
    {
        time_t tv_sec;
        long tv_usec;
    };
#endif

#include <lwip/sockets.h>

#if LWIP_SOCKET != 1
// Sockets support disabled in LWIP by default
#error Set LWIP_SOCKET variable to 1 (in lwipopts.h)
#endif
#endif

#if defined(MG_ENABLE_RL) && MG_ENABLE_RL
#include <rl_net.h>

#define MG_ENABLE_CUSTOM_MILLIS 1
#define closesocket(x) closesocket(x)
#define mkdir(a, b) (-1)

#define TCP_NODELAY SO_KEEPALIVE

#define MG_SOCK_ERR(errcode) ((errcode) < 0 ? (errcode) : 0)

#define MG_SOCK_PENDING(errcode)                                  \
    ((errcode) == BSD_EWOULDBLOCK || (errcode) == BSD_EALREADY || \
     (errcode) == BSD_EINPROGRESS)

#define MG_SOCK_RESET(errcode) \
    ((errcode) == BSD_ECONNABORTED || (errcode) == BSD_ECONNRESET)

#define MG_SOCK_INTR(fd) 0

#define socklen_t int
#endif

#ifndef MG_ENABLE_LOG
#define MG_ENABLE_LOG 1
#endif

#ifndef MG_ENABLE_TCPIP
#define MG_ENABLE_TCPIP 0 // Mongoose built-in network stack
#endif

#ifndef MG_ENABLE_LWIP
#define MG_ENABLE_LWIP 0 // lWIP network stack
#endif

#ifndef MG_ENABLE_FREERTOS_TCP
#define MG_ENABLE_FREERTOS_TCP 0 // Amazon FreeRTOS-TCP network stack
#endif

#ifndef MG_ENABLE_RL
#define MG_ENABLE_RL 0 // ARM MDK network stack
#endif

#ifndef MG_ENABLE_SOCKET
#define MG_ENABLE_SOCKET !MG_ENABLE_TCPIP
#endif

#ifndef MG_ENABLE_POLL
#define MG_ENABLE_POLL 0
#endif

#ifndef MG_ENABLE_EPOLL
#define MG_ENABLE_EPOLL 0
#endif

#ifndef MG_ENABLE_FATFS
#define MG_ENABLE_FATFS 0
#endif

#ifndef MG_ENABLE_MBEDTLS
#define MG_ENABLE_MBEDTLS 0
#endif

#ifndef MG_ENABLE_OPENSSL
#define MG_ENABLE_OPENSSL 0
#endif

#ifndef MG_ENABLE_CUSTOM_TLS
#define MG_ENABLE_CUSTOM_TLS 0
#endif

#ifndef MG_ENABLE_SSI
#define MG_ENABLE_SSI 0
#endif

#ifndef MG_ENABLE_IPV6
#define MG_ENABLE_IPV6 0
#endif

#ifndef MG_ENABLE_MD5
#define MG_ENABLE_MD5 1
#endif

// Set MG_ENABLE_WINSOCK=0 for Win32 builds with external IP stack (like LWIP)
#ifndef MG_ENABLE_WINSOCK
#define MG_ENABLE_WINSOCK 1
#endif

#ifndef MG_ENABLE_DIRLIST
#define MG_ENABLE_DIRLIST 0
#endif

#ifndef MG_ENABLE_CUSTOM_RANDOM
#define MG_ENABLE_CUSTOM_RANDOM 0
#endif

#ifndef MG_ENABLE_CUSTOM_MILLIS
#define MG_ENABLE_CUSTOM_MILLIS 0
#endif

#ifndef MG_ENABLE_PACKED_FS
#define MG_ENABLE_PACKED_FS 0
#endif

#ifndef MG_ENABLE_ASSERT
#define MG_ENABLE_ASSERT 0
#endif

#ifndef MG_IO_SIZE
#define MG_IO_SIZE 2048 // Granularity of the send/recv IO buffer growth
#endif

#ifndef MG_MAX_RECV_SIZE
#define MG_MAX_RECV_SIZE (3 * 1024 * 1024) // Maximum recv IO buffer size
#endif

#ifndef MG_DATA_SIZE
#define MG_DATA_SIZE 32 // struct mg_connection :: data size
#endif

#ifndef MG_MAX_HTTP_HEADERS
#define MG_MAX_HTTP_HEADERS 40
#endif

#ifndef MG_HTTP_INDEX
#define MG_HTTP_INDEX "index.html"
#endif

#ifndef MG_PATH_MAX
#ifdef PATH_MAX
#define MG_PATH_MAX PATH_MAX
#else
#define MG_PATH_MAX 128
#endif
#endif

#ifndef MG_SOCK_LISTEN_BACKLOG_SIZE
#define MG_SOCK_LISTEN_BACKLOG_SIZE 3
#endif

#ifndef MG_DIRSEP
#define MG_DIRSEP '/'
#endif

#ifndef MG_ENABLE_FILE
#if defined(FOPEN_MAX)
#define MG_ENABLE_FILE 1
#else
#define MG_ENABLE_FILE 0
#endif
#endif

#ifndef MG_INVALID_SOCKET
#define MG_INVALID_SOCKET (-1)
#endif

#ifndef MG_SOCKET_TYPE
#define MG_SOCKET_TYPE int
#endif

#ifndef MG_SOCKET_ERRNO
#define MG_SOCKET_ERRNO errno
#endif

#if MG_ENABLE_EPOLL
#define MG_EPOLL_ADD(c)                                                      \
    do                                                                       \
    {                                                                        \
        struct epoll_event ev = {EPOLLIN | EPOLLERR | EPOLLHUP, {c}};        \
        epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_ADD, (int)(size_t)c->fd, &ev); \
    } while (0)
#define MG_EPOLL_MOD(c, wr)                                                  \
    do                                                                       \
    {                                                                        \
        struct epoll_event ev = {EPOLLIN | EPOLLERR | EPOLLHUP, {c}};        \
        if (wr)                                                              \
            ev.events |= EPOLLOUT;                                           \
        epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_MOD, (int)(size_t)c->fd, &ev); \
    } while (0)
#else
#define MG_EPOLL_ADD(c)
#define MG_EPOLL_MOD(c, wr)
#endif

    struct mg_str
    {
        const char *ptr; // Pointer to string data
        size_t len;      // String len
    };

#define MG_NULL_STR \
    {               \
        NULL, 0     \
    }

#define MG_C_STR(a)        \
    {                      \
        (a), sizeof(a) - 1 \
    }

// Using macro to avoid shadowing C++ struct constructor, see #1298
#define mg_str(s) mg_str_s(s)
	 
    struct mg_str mg_str(const char *s);
    struct mg_str mg_str_n(const char *s, size_t n);
    int mg_lower(const char *s);
    int mg_ncasecmp(const char *s1, const char *s2, size_t len);
    int mg_casecmp(const char *s1, const char *s2);
    int mg_vcmp(const struct mg_str *s1, const char *s2);
    int mg_vcasecmp(const struct mg_str *str1, const char *str2);
    int mg_strcmp(const struct mg_str str1, const struct mg_str str2);
    struct mg_str mg_strstrip(struct mg_str s);
    struct mg_str mg_strdup(const struct mg_str s);
    const char *mg_strstr(const struct mg_str haystack, const struct mg_str needle);
    bool mg_match(struct mg_str str, struct mg_str pattern, struct mg_str *caps);
    bool mg_globmatch(const char *pattern, size_t plen, const char *s, size_t n);
    bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v);
    bool mg_split(struct mg_str *s, struct mg_str *k, struct mg_str *v, char delim);
    char *mg_hex(const void *buf, size_t len, char *dst);
    void mg_unhex(const char *buf, size_t len, unsigned char *to);
    unsigned long mg_unhexn(const char *s, size_t len);
    int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip);
    int64_t mg_to64(struct mg_str str);
    uint64_t mg_tou64(struct mg_str str);
    char *mg_remove_double_dots(char *s);

    // Single producer, single consumer non-blocking queue
    //
    // Producer:
    //    char *buf;
    //    while (mg_queue_book(q, &buf) < len) WAIT();  // Wait for space
    //    memcpy(buf, my_data, len);   // Copy data to the queue
    //    mg_queue_add(q, len);
    //
    // Consumer:
    //    char *buf;
    //    while ((len = mg_queue_get(q, &buf)) == 0) WAIT();
    //    mg_hexdump(buf, len); // Handle message
    //    mg_queue_del(q, len);
    //
    struct mg_queue
    {
        char *buf;
        size_t size;
        volatile size_t tail;
        volatile size_t head;
    };

    void mg_queue_init(struct mg_queue *, char *, size_t);       // Init queue
    size_t mg_queue_book(struct mg_queue *, char **buf, size_t); // Reserve space
    void mg_queue_add(struct mg_queue *, size_t);                // Add new message
    size_t mg_queue_next(struct mg_queue *, char **);            // Get oldest message
    void mg_queue_del(struct mg_queue *, size_t);                // Delete oldest message

    typedef void (*mg_pfn_t)(char, void *);                 // Output function
    typedef size_t (*mg_pm_t)(mg_pfn_t, void *, va_list *); // %M printer

    size_t mg_vxprintf(void (*)(char, void *), void *, const char *fmt, va_list *);
    size_t mg_xprintf(void (*fn)(char, void *), void *, const char *fmt, ...);

    // Convenience wrappers around mg_xprintf
    size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list *ap);
    size_t mg_snprintf(char *, size_t, const char *fmt, ...);
    char *mg_vmprintf(const char *fmt, va_list *ap);
    char *mg_mprintf(const char *fmt, ...);
    size_t mg_queue_vprintf(struct mg_queue *, const char *fmt, va_list *);
    size_t mg_queue_printf(struct mg_queue *, const char *fmt, ...);

    // %M print helper functions
    size_t mg_print_ip(void (*out)(char, void *), void *arg, va_list *ap);
    size_t mg_print_ip_port(void (*out)(char, void *), void *arg, va_list *ap);
    size_t mg_print_ip4(void (*out)(char, void *), void *arg, va_list *ap);
    size_t mg_print_ip6(void (*out)(char, void *), void *arg, va_list *ap);
    size_t mg_print_mac(void (*out)(char, void *), void *arg, va_list *ap);

    // Various output functions
    void mg_pfn_iobuf(char ch, void *param); // param: struct mg_iobuf *
    void mg_pfn_stdout(char c, void *param); // param: ignored

    enum
    {
        MG_LL_NONE,
        MG_LL_ERROR,
        MG_LL_INFO,
        MG_LL_DEBUG,
        MG_LL_VERBOSE
    };
    void mg_log(const char *fmt, ...);
    bool mg_log_prefix(int ll, const char *file, int line, const char *fname);
    void mg_log_set(int log_level);
    void mg_hexdump(const void *buf, size_t len);
    void mg_log_set_fn(mg_pfn_t fn, void *param);

#if MG_ENABLE_LOG
#define MG_LOG(level, args)                                       \
    do                                                            \
    {                                                             \
        if (mg_log_prefix((level), __FILE__, __LINE__, __func__)) \
            mg_log args;                                          \
    } while (0)
#else
#define MG_LOG(level, args) \
    do                      \
    {                       \
        if (0)              \
            mg_log args;    \
    } while (0)
#endif

#define MG_ERROR(args) MG_LOG(MG_LL_ERROR, args)
#define MG_INFO(args) MG_LOG(MG_LL_INFO, args)
#define MG_DEBUG(args) MG_LOG(MG_LL_DEBUG, args)
#define MG_VERBOSE(args) MG_LOG(MG_LL_VERBOSE, args)

    struct mg_timer
    {
        unsigned long id;      // Timer ID
        uint64_t period_ms;    // Timer period in milliseconds
        uint64_t expire;       // Expiration timestamp in milliseconds
        unsigned flags;        // Possible flags values below
#define MG_TIMER_ONCE 0        // Call function once
#define MG_TIMER_REPEAT 1      // Call function periodically
#define MG_TIMER_RUN_NOW 2     // Call immediately when timer is set
        void (*fn)(void *);    // Function to call
        void *arg;             // Function argument
        struct mg_timer *next; // Linkage
    };

    void mg_timer_init(struct mg_timer **head, struct mg_timer *timer,
                       uint64_t milliseconds, unsigned flags, void (*fn)(void *),
                       void *arg);
    void mg_timer_free(struct mg_timer **head, struct mg_timer *);
    void mg_timer_poll(struct mg_timer **head, uint64_t new_ms);
    bool mg_timer_expired(uint64_t *expiration, uint64_t period, uint64_t now);

    enum
    {
        MG_FS_READ = 1,
        MG_FS_WRITE = 2,
        MG_FS_DIR = 4
    };

    // Filesystem API functions
    // st() returns MG_FS_* flags and populates file size and modification time
    // ls() calls fn() for every directory entry, allowing to list a directory
    //
    // NOTE: UNIX-style shorthand names for the API functions are deliberately
    // chosen to avoid conflicts with some libraries that make macros for e.g.
    // stat(), write(), read() calls.
    struct mg_fs
    {
        int (*st)(const char *path, size_t *size, time_t *mtime); // stat file
        void (*ls)(const char *path, void (*fn)(const char *, void *), void *);
        void *(*op)(const char *path, int flags);            // Open file
        void (*cl)(void *fd);                                // Close file
        size_t (*rd)(void *fd, void *buf, size_t len);       // Read file
        size_t (*wr)(void *fd, const void *buf, size_t len); // Write file
        size_t (*sk)(void *fd, size_t offset);               // Set file position
        bool (*mv)(const char *from, const char *to);        // Rename file
        bool (*rm)(const char *path);                        // Delete file
        bool (*mkd)(const char *path);                       // Create directory
    };

    extern struct mg_fs mg_fs_posix;  // POSIX open/close/read/write/seek
    extern struct mg_fs mg_fs_packed; // Packed FS, see examples/device-dashboard
    extern struct mg_fs mg_fs_fat;    // FAT FS

    // File descriptor
    struct mg_fd
    {
        void *fd;
        struct mg_fs *fs;
    };

    struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags);
    void mg_fs_close(struct mg_fd *fd);
    char *mg_file_read(struct mg_fs *fs, const char *path, size_t *size);
    bool mg_file_write(struct mg_fs *fs, const char *path, const void *, size_t);
    bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...);

#if MG_ENABLE_ASSERT
#include <assert.h>
#elif !defined(assert)
#define assert(x)
#endif

    void mg_random(void *buf, size_t len);
    char *mg_random_str(char *buf, size_t len);
    uint16_t mg_ntohs(uint16_t net);
    uint32_t mg_ntohl(uint32_t net);
    uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len);
    uint64_t mg_millis(void);

#define mg_htons(x) mg_ntohs(x)
#define mg_htonl(x) mg_ntohl(x)

#define MG_U32(a, b, c, d)                                       \
    (((uint32_t)((a)&255) << 24) | ((uint32_t)((b)&255) << 16) | \
     ((uint32_t)((c)&255) << 8) | (uint32_t)((d)&255))

// For printing IPv4 addresses: printf("%d.%d.%d.%d\n", MG_IPADDR_PARTS(&ip))
#define MG_U8P(ADDR) ((uint8_t *)(ADDR))
#define MG_IPADDR_PARTS(ADDR) \
    MG_U8P(ADDR)              \
    [0], MG_U8P(ADDR)[1], MG_U8P(ADDR)[2], MG_U8P(ADDR)[3]

// Linked list management macros
#define LIST_ADD_HEAD(type_, head_, elem_) \
    do                                     \
    {                                      \
        (elem_)->next = (*head_);          \
        *(head_) = (elem_);                \
    } while (0)

#define LIST_ADD_TAIL(type_, head_, elem_) \
    do                                     \
    {                                      \
        type_ **h = head_;                 \
        while (*h != NULL)                 \
            h = &(*h)->next;               \
        *h = (elem_);                      \
    } while (0)

#define LIST_DELETE(type_, head_, elem_) \
    do                                   \
    {                                    \
        type_ **h = head_;               \
        while (*h != (elem_))            \
            h = &(*h)->next;             \
        *h = (elem_)->next;              \
    } while (0)

    unsigned short mg_url_port(const char *url);
    int mg_url_is_ssl(const char *url);
    struct mg_str mg_url_host(const char *url);
    struct mg_str mg_url_user(const char *url);
    struct mg_str mg_url_pass(const char *url);
    const char *mg_url_uri(const char *url);

    struct mg_iobuf
    {
        unsigned char *buf; // Pointer to stored data
        size_t size;        // Total size available
        size_t len;         // Current number of bytes
        size_t align;       // Alignment during allocation
    };

    int mg_iobuf_init(struct mg_iobuf *, size_t, size_t);
    int mg_iobuf_resize(struct mg_iobuf *, size_t);
    void mg_iobuf_free(struct mg_iobuf *);
    size_t mg_iobuf_add(struct mg_iobuf *, size_t, const void *, size_t);
    size_t mg_iobuf_del(struct mg_iobuf *, size_t ofs, size_t len);

    int mg_base64_update(unsigned char p, char *to, int len);
    int mg_base64_final(char *to, int len);
    int mg_base64_encode(const unsigned char *p, int n, char *to);
    int mg_base64_decode(const char *src, int n, char *dst);

    typedef struct
    {
        uint32_t buf[4];
        uint32_t bits[2];
        unsigned char in[64];
    } mg_md5_ctx;

    void mg_md5_init(mg_md5_ctx *c);
    void mg_md5_update(mg_md5_ctx *c, const unsigned char *data, size_t len);
    void mg_md5_final(mg_md5_ctx *c, unsigned char[16]);

    typedef struct
    {
        uint32_t state[5];
        uint32_t count[2];
        unsigned char buffer[64];
    } mg_sha1_ctx;

    void mg_sha1_init(mg_sha1_ctx *);
    void mg_sha1_update(mg_sha1_ctx *, const unsigned char *data, size_t len);
    void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *);

    struct mg_connection;
    typedef void (*mg_event_handler_t)(struct mg_connection *, int ev,
                                       void *ev_data, void *fn_data);
    void mg_call(struct mg_connection *c, int ev, void *ev_data);
    void mg_error(struct mg_connection *c, const char *fmt, ...);

    enum
    {
        MG_EV_ERROR,      // Error                        char *error_message
        MG_EV_OPEN,       // Connection created           NULL
        MG_EV_POLL,       // mg_mgr_poll iteration        uint64_t *uptime_millis
        MG_EV_RESOLVE,    // Host name is resolved        NULL
        MG_EV_CONNECT,    // Connection established       NULL
        MG_EV_ACCEPT,     // Connection accepted          NULL
        MG_EV_TLS_HS,     // TLS handshake succeeded      NULL
        MG_EV_READ,       // Data received from socket    long *bytes_read
        MG_EV_WRITE,      // Data written to socket       long *bytes_written
        MG_EV_CLOSE,      // Connection closed            NULL
        MG_EV_HTTP_MSG,   // HTTP request/response        struct mg_http_message *
        MG_EV_HTTP_CHUNK, // HTTP chunk (partial msg)     struct mg_http_message *
        MG_EV_WS_OPEN,    // Websocket handshake done     struct mg_http_message *
        MG_EV_WS_MSG,     // Websocket msg, text or bin   struct mg_ws_message *
        MG_EV_WS_CTL,     // Websocket control msg        struct mg_ws_message *
        MG_EV_MQTT_CMD,   // MQTT low-level command       struct mg_mqtt_message *
        MG_EV_MQTT_MSG,   // MQTT PUBLISH received        struct mg_mqtt_message *
        MG_EV_MQTT_OPEN,  // MQTT CONNACK received        int *connack_status_code
        MG_EV_SNTP_TIME,  // SNTP time received           uint64_t *epoch_millis
        MG_EV_USER        // Starting ID for user events
    };

    struct mg_dns
    {
        const char *url;         // DNS server URL
        struct mg_connection *c; // DNS server connection
    };

    struct mg_addr
    {
        uint16_t port;   // TCP or UDP port in network byte order
        uint32_t ip;     // IP address in network byte order
        uint8_t ip6[16]; // IPv6 address
        bool is_ip6;     // True when address is IPv6 address
    };

    struct mg_mgr
    {
        struct mg_connection *conns; // List of active connections
        struct mg_dns dns4;          // DNS for IPv4
        struct mg_dns dns6;          // DNS for IPv6
        int dnstimeout;              // DNS resolve timeout in milliseconds
        bool use_dns6;               // Use DNS6 server by default, see #1532
        unsigned long nextid;        // Next connection ID
        unsigned long timerid;       // Next timer ID
        void *userdata;              // Arbitrary user data pointer
        uint16_t mqtt_id;            // MQTT IDs for pub/sub
        void *active_dns_requests;   // DNS requests in progress
        struct mg_timer *timers;     // Active timers
        int epoll_fd;                // Used when MG_EPOLL_ENABLE=1
        void *priv;                  // Used by the MIP stack
        size_t extraconnsize;        // Used by the MIP stack
#if MG_ENABLE_FREERTOS_TCP
        SocketSet_t ss; // NOTE(lsm): referenced from socket struct
#endif
    };

    struct mg_connection
    {
        struct mg_connection *next; // Linkage in struct mg_mgr :: connections
        struct mg_mgr *mgr;         // Our container
        struct mg_addr loc;         // Local address
        struct mg_addr rem;         // Remote address
        void *fd;                   // Connected socket, or LWIP data
        unsigned long id;           // Auto-incrementing unique connection ID
        struct mg_iobuf recv;       // Incoming data
        struct mg_iobuf send;       // Outgoing data
        mg_event_handler_t fn;      // User-specified event handler function
        void *fn_data;              // User-specified function parameter
        mg_event_handler_t pfn;     // Protocol-specific handler function
        void *pfn_data;             // Protocol-specific function parameter
        char data[MG_DATA_SIZE];    // Arbitrary connection data
        void *tls;                  // TLS specific data
        unsigned is_listening : 1;  // Listening connection
        unsigned is_client : 1;     // Outbound (client) connection
        unsigned is_accepted : 1;   // Accepted (server) connection
        unsigned is_resolving : 1;  // Non-blocking DNS resolution is in progress
        unsigned is_arplooking : 1; // Non-blocking ARP resolution is in progress
        unsigned is_connecting : 1; // Non-blocking connect is in progress
        unsigned is_tls : 1;        // TLS-enabled connection
        unsigned is_tls_hs : 1;     // TLS handshake is in progress
        unsigned is_udp : 1;        // UDP connection
        unsigned is_websocket : 1;  // WebSocket connection
        unsigned is_mqtt5 : 1;      // For MQTT connection, v5 indicator
        unsigned is_hexdumping : 1; // Hexdump in/out traffic
        unsigned is_draining : 1;   // Send remaining data, then close and free
        unsigned is_closing : 1;    // Close and free the connection immediately
        unsigned is_full : 1;       // Stop reads, until cleared
        unsigned is_resp : 1;       // Response is still being generated
        unsigned is_readable : 1;   // Connection is ready to read
        unsigned is_writable : 1;   // Connection is ready to write
    };

    void mg_mgr_poll(struct mg_mgr *, int ms);
    void mg_mgr_init(struct mg_mgr *);
    void mg_mgr_free(struct mg_mgr *);

    struct mg_connection *mg_listen(struct mg_mgr *, const char *url,
                                    mg_event_handler_t fn, void *fn_data);
    struct mg_connection *mg_connect(struct mg_mgr *, const char *url,
                                     mg_event_handler_t fn, void *fn_data);
    struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd,
                                    mg_event_handler_t fn, void *fn_data);
    void mg_connect_resolved(struct mg_connection *);
    bool mg_send(struct mg_connection *, const void *, size_t);
    size_t mg_printf(struct mg_connection *, const char *fmt, ...);
    size_t mg_vprintf(struct mg_connection *, const char *fmt, va_list *ap);
    bool mg_aton(struct mg_str str, struct mg_addr *addr);
    int mg_mkpipe(struct mg_mgr *, mg_event_handler_t, void *, bool udp);

    // These functions are used to integrate with custom network stacks
    struct mg_connection *mg_alloc_conn(struct mg_mgr *);
    void mg_close_conn(struct mg_connection *c);
    bool mg_open_listener(struct mg_connection *c, const char *url);

    // Utility functions
    struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds,
                                  unsigned flags, void (*fn)(void *), void *arg);

    // Low-level IO primives used by TLS layer
    enum
    {
        MG_IO_ERR = -1,
        MG_IO_WAIT = -2,
        MG_IO_RESET = -3
    };
    long mg_io_send(struct mg_connection *c, const void *buf, size_t len);
    long mg_io_recv(struct mg_connection *c, void *buf, size_t len);

    struct mg_http_header
    {
        struct mg_str name;  // Header name
        struct mg_str value; // Header value
    };

    struct mg_http_message
    {
        struct mg_str method, uri, query, proto;            // Request/response line
        struct mg_http_header headers[MG_MAX_HTTP_HEADERS]; // Headers
        struct mg_str body;                                 // Body
        struct mg_str head;                                 // Request + headers
        struct mg_str chunk;                                // Chunk for chunked encoding,  or partial body
        struct mg_str message;                              // Request + headers + body
    };

    // Parameter for mg_http_serve_dir()
    struct mg_http_serve_opts
    {
        const char *root_dir;      // Web root directory, must be non-NULL
        const char *ssi_pattern;   // SSI file name pattern, e.g. #.shtml
        const char *extra_headers; // Extra HTTP headers to add in responses
        const char *mime_types;    // Extra mime types, ext1=type1,ext2=type2,..
        const char *page404;       // Path to the 404 page, or NULL by default
        struct mg_fs *fs;          // Filesystem implementation. Use NULL for POSIX
    };

    // Parameter for mg_http_next_multipart
    struct mg_http_part
    {
        struct mg_str name;     // Form field name
        struct mg_str filename; // Filename for file uploads
        struct mg_str body;     // Part contents
    };

    int mg_http_parse(const char *s, size_t len, struct mg_http_message *);
    int mg_http_get_request_len(const unsigned char *buf, size_t buf_len);
    void mg_http_printf_chunk(struct mg_connection *cnn, const char *fmt, ...);
    void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len);
    void mg_http_delete_chunk(struct mg_connection *c, struct mg_http_message *hm);
    struct mg_connection *mg_http_listen(struct mg_mgr *, const char *url,
                                         mg_event_handler_t fn, void *fn_data);
    struct mg_connection *mg_http_connect(struct mg_mgr *, const char *url,
                                          mg_event_handler_t fn, void *fn_data);
    void mg_http_serve_dir(struct mg_connection *, struct mg_http_message *hm,
                           const struct mg_http_serve_opts *);
    void mg_http_serve_file(struct mg_connection *, struct mg_http_message *hm,
                            const char *path, const struct mg_http_serve_opts *);
    void mg_http_reply(struct mg_connection *, int status_code, const char *headers,
                       const char *body_fmt, ...);
    struct mg_str *mg_http_get_header(struct mg_http_message *, const char *name);
    struct mg_str mg_http_var(struct mg_str buf, struct mg_str name);
    int mg_http_get_var(const struct mg_str *, const char *name, char *, size_t);
    int mg_url_decode(const char *s, size_t n, char *to, size_t to_len, int form);
    size_t mg_url_encode(const char *s, size_t n, char *buf, size_t len);
    void mg_http_creds(struct mg_http_message *, char *, size_t, char *, size_t);
    bool mg_http_match_uri(const struct mg_http_message *, const char *glob);
    long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm,
                        struct mg_fs *fs, const char *path, size_t max_size);
    void mg_http_bauth(struct mg_connection *, const char *user, const char *pass);
    struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v);
    size_t mg_http_next_multipart(struct mg_str, size_t, struct mg_http_part *);
    int mg_http_status(const struct mg_http_message *hm);
    void mg_hello(const char *url);

    void mg_http_serve_ssi(struct mg_connection *c, const char *root,
                           const char *fullpath);

    struct mg_tls_opts
    {
        const char *ca;        // CA certificate file. For both listeners and clients
        const char *crl;       // Certificate Revocation List. For clients
        const char *cert;      // Certificate
        const char *certkey;   // Certificate key
        const char *ciphers;   // Cipher list
        struct mg_str srvname; // If not empty, enables server name verification
        struct mg_fs *fs;      // FS API for reading certificate files
    };

    void mg_tls_init(struct mg_connection *, const struct mg_tls_opts *);
    void mg_tls_free(struct mg_connection *);
    long mg_tls_send(struct mg_connection *, const void *buf, size_t len);
    long mg_tls_recv(struct mg_connection *, void *buf, size_t len);
    size_t mg_tls_pending(struct mg_connection *);
    void mg_tls_handshake(struct mg_connection *);

#if MG_ENABLE_MBEDTLS
#include <mbedtls/debug.h>
#include <mbedtls/net_sockets.h>
#include <mbedtls/ssl.h>

    struct mg_tls
    {
        char *cafile;            // CA certificate path
        mbedtls_x509_crt ca;     // Parsed CA certificate
        mbedtls_x509_crt cert;   // Parsed certificate
        mbedtls_ssl_context ssl; // SSL/TLS context
        mbedtls_ssl_config conf; // SSL-TLS config
        mbedtls_pk_context pk;   // Private key context
    };
#endif

#if MG_ENABLE_OPENSSL

#include <openssl/err.h>
#include <openssl/ssl.h>

    struct mg_tls
    {
        SSL_CTX *ctx;
        SSL *ssl;
    };
#endif

#define WEBSOCKET_OP_CONTINUE 0
#define WEBSOCKET_OP_TEXT 1
#define WEBSOCKET_OP_BINARY 2
#define WEBSOCKET_OP_CLOSE 8
#define WEBSOCKET_OP_PING 9
#define WEBSOCKET_OP_PONG 10

    struct mg_ws_message
    {
        struct mg_str data; // Websocket message data
        uint8_t flags;      // Websocket message flags
    };

    struct mg_connection *mg_ws_connect(struct mg_mgr *, const char *url,
                                        mg_event_handler_t fn, void *fn_data,
                                        const char *fmt, ...);
    void mg_ws_upgrade(struct mg_connection *, struct mg_http_message *,
                       const char *fmt, ...);
    size_t mg_ws_send(struct mg_connection *, const void *buf, size_t len, int op);
    size_t mg_ws_wrap(struct mg_connection *, size_t len, int op);
    size_t mg_ws_printf(struct mg_connection *c, int op, const char *fmt, ...);
    size_t mg_ws_vprintf(struct mg_connection *c, int op, const char *fmt,
                         va_list *);

    struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const char *url,
                                          mg_event_handler_t fn, void *fn_data);
    void mg_sntp_request(struct mg_connection *c);
    int64_t mg_sntp_parse(const unsigned char *buf, size_t len);

#define MQTT_CMD_CONNECT 1
#define MQTT_CMD_CONNACK 2
#define MQTT_CMD_PUBLISH 3
#define MQTT_CMD_PUBACK 4
#define MQTT_CMD_PUBREC 5
#define MQTT_CMD_PUBREL 6
#define MQTT_CMD_PUBCOMP 7
#define MQTT_CMD_SUBSCRIBE 8
#define MQTT_CMD_SUBACK 9
#define MQTT_CMD_UNSUBSCRIBE 10
#define MQTT_CMD_UNSUBACK 11
#define MQTT_CMD_PINGREQ 12
#define MQTT_CMD_PINGRESP 13
#define MQTT_CMD_DISCONNECT 14
#define MQTT_CMD_AUTH 15

    enum
    {
        MQTT_OK,
        MQTT_INCOMPLETE,
        MQTT_MALFORMED
    };

    struct mg_mqtt_opts
    {
        struct mg_str user;         // Username, can be empty
        struct mg_str pass;         // Password, can be empty
        struct mg_str client_id;    // Client ID
        struct mg_str will_topic;   // Will topic
        struct mg_str will_message; // Will message
        uint8_t will_qos;           // Will message quality of service
        uint8_t version;            // Can be 4 (3.1.1), or 5. If 0, assume 4.
        uint16_t keepalive;         // Keep-alive timer in seconds
        bool will_retain;           // Retain last will
        bool clean;                 // Use clean session, 0 or 1
    };

    struct mg_mqtt_message
    {
        struct mg_str topic; // Parsed topic
        struct mg_str data;  // Parsed message
        struct mg_str dgram; // Whole MQTT datagram, including headers
        uint16_t id;         // Set for PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK, PUBLISH
        uint8_t cmd;         // MQTT command, one of MQTT_CMD_*
        uint8_t qos;         // Quality of service
        uint8_t ack;         // Connack return code. 0 - success
    };

    struct mg_connection *mg_mqtt_connect(struct mg_mgr *, const char *url,
                                          const struct mg_mqtt_opts *opts,
                                          mg_event_handler_t fn, void *fn_data);
    struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url,
                                         mg_event_handler_t fn, void *fn_data);
    void mg_mqtt_login(struct mg_connection *c, const struct mg_mqtt_opts *opts);
    void mg_mqtt_pub(struct mg_connection *c, struct mg_str topic,
                     struct mg_str data, int qos, bool retain);
    void mg_mqtt_sub(struct mg_connection *, struct mg_str topic, int qos);
    int mg_mqtt_parse(const uint8_t *, size_t, uint8_t, struct mg_mqtt_message *);
    void mg_mqtt_send_header(struct mg_connection *, uint8_t cmd, uint8_t flags,
                             uint32_t len);
    void mg_mqtt_ping(struct mg_connection *);
    void mg_mqtt_pong(struct mg_connection *);
    void mg_mqtt_disconnect(struct mg_connection *);

    // Mongoose sends DNS queries that contain only one question:
    // either A (IPv4) or AAAA (IPv6) address lookup.
    // Therefore, we expect zero or one answer.
    // If `resolved` is true, then `addr` contains resolved IPv4 or IPV6 address.
    struct mg_dns_message
    {
        uint16_t txnid;      // Transaction ID
        bool resolved;       // Resolve successful, addr is set
        struct mg_addr addr; // Resolved address
        char name[256];      // Host name
    };

    struct mg_dns_header
    {
        uint16_t txnid; // Transaction ID
        uint16_t flags;
        uint16_t num_questions;
        uint16_t num_answers;
        uint16_t num_authority_prs;
        uint16_t num_other_prs;
    };

    // DNS resource record
    struct mg_dns_rr
    {
        uint16_t nlen;   // Name or pointer length
        uint16_t atype;  // Address type
        uint16_t aclass; // Address class
        uint16_t alen;   // Address length
    };

    void mg_resolve(struct mg_connection *, const char *url);
    void mg_resolve_cancel(struct mg_connection *);
    bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *);
    size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs,
                           bool is_question, struct mg_dns_rr *);

#ifndef MG_JSON_MAX_DEPTH
#define MG_JSON_MAX_DEPTH 30
#endif

    // Error return values - negative. Successful returns are >= 0
    enum
    {
        MG_JSON_TOO_DEEP = -1,
        MG_JSON_INVALID = -2,
        MG_JSON_NOT_FOUND = -3
    };
    int mg_json_get(struct mg_str json, const char *path, int *toklen);

    bool mg_json_get_num(struct mg_str json, const char *path, double *v);
    bool mg_json_get_bool(struct mg_str json, const char *path, bool *v);
    long mg_json_get_long(struct mg_str json, const char *path, long dflt);
    char *mg_json_get_str(struct mg_str json, const char *path);
    char *mg_json_get_hex(struct mg_str json, const char *path, int *len);
    char *mg_json_get_b64(struct mg_str json, const char *path, int *len);

    // JSON-RPC request descriptor
    struct mg_rpc_req
    {
        struct mg_rpc **head; // RPC handlers list head
        struct mg_rpc *rpc;   // RPC handler being called
        mg_pfn_t pfn;         // Response printing function
        void *pfn_data;       // Response printing function data
        void *req_data;       // Arbitrary request data
        struct mg_str frame;  // Request, e.g. {"id":1,"method":"add","params":[1,2]}
    };

    // JSON-RPC method handler
    struct mg_rpc
    {
        struct mg_rpc *next;             // Next in list
        struct mg_str method;            // Method pattern
        void (*fn)(struct mg_rpc_req *); // Handler function
        void *fn_data;                   // Handler function argument
    };

    void mg_rpc_add(struct mg_rpc **head, struct mg_str method_pattern,
                    void (*handler)(struct mg_rpc_req *), void *handler_data);
    void mg_rpc_del(struct mg_rpc **head, void (*handler)(struct mg_rpc_req *));
    void mg_rpc_process(struct mg_rpc_req *);

    // Helper functions to print result or error frame
    void mg_rpc_ok(struct mg_rpc_req *, const char *fmt, ...);
    void mg_rpc_vok(struct mg_rpc_req *, const char *fmt, va_list *ap);
    void mg_rpc_err(struct mg_rpc_req *, int code, const char *fmt, ...);
    void mg_rpc_verr(struct mg_rpc_req *, int code, const char *fmt, va_list *);
    void mg_rpc_list(struct mg_rpc_req *r);

    struct mg_tcpip_if; // MIP network interface

    struct mg_tcpip_driver
    {
        bool (*init)(struct mg_tcpip_if *);                        // Init driver
        size_t (*tx)(const void *, size_t, struct mg_tcpip_if *);  // Transmit frame
        size_t (*rx)(void *buf, size_t len, struct mg_tcpip_if *); // Receive frame
        bool (*up)(struct mg_tcpip_if *);                          // Up/down status
    };

    // Network interface
    struct mg_tcpip_if
    {
        uint8_t mac[6];                 // MAC address. Must be set to a valid MAC
        uint32_t ip, mask, gw;          // IP address, mask, default gateway
        struct mg_str tx;               // Output (TX) buffer
        bool enable_dhcp_client;        // Enable DCHP client
        bool enable_dhcp_server;        // Enable DCHP server
        bool enable_crc32_check;        // Do a CRC check on rx frames and strip it
        bool enable_mac_check;          // Do a MAC check on rx frames
        struct mg_tcpip_driver *driver; // Low level driver
        void *driver_data;              // Driver-specific data
        struct mg_mgr *mgr;             // Mongoose event manager
        struct mg_queue recv_queue;     // Receive queue

        // Internal state, user can use it but should not change it
        uint8_t gwmac[6];        // Router's MAC
        uint64_t now;            // Current time
        uint64_t timer_1000ms;   // 1000 ms timer: for DHCP and link state
        uint64_t lease_expire;   // Lease expiration time
        uint16_t eport;          // Next ephemeral port
        volatile uint32_t ndrop; // Number of received, but dropped frames
        volatile uint32_t nrecv; // Number of received frames
        volatile uint32_t nsent; // Number of transmitted frames
        volatile uint32_t nerr;  // Number of driver errors
        uint8_t state;           // Current state
#define MG_TCPIP_STATE_DOWN 0    // Interface is down
#define MG_TCPIP_STATE_UP 1      // Interface is up
#define MG_TCPIP_STATE_READY 2   // Interface is up and has IP
    };

    void mg_tcpip_init(struct mg_mgr *, struct mg_tcpip_if *);
    void mg_tcpip_free(struct mg_tcpip_if *);
    void mg_tcpip_qwrite(void *buf, size_t len, struct mg_tcpip_if *ifp);

    extern struct mg_tcpip_driver mg_tcpip_driver_stm32;
    extern struct mg_tcpip_driver mg_tcpip_driver_w5500;
    extern struct mg_tcpip_driver mg_tcpip_driver_tm4c;
    extern struct mg_tcpip_driver mg_tcpip_driver_stm32h;
    extern struct mg_tcpip_driver mg_tcpip_driver_imxrt;

    // Drivers that require SPI, can use this SPI abstraction
    struct mg_tcpip_spi
    {
        void *spi;                       // Opaque SPI bus descriptor
        void (*begin)(void *);           // SPI begin: slave select low
        void (*end)(void *);             // SPI end: slave select high
        uint8_t (*txn)(void *, uint8_t); // SPI transaction: write 1 byte, read reply
    };

#if MG_ENABLE_TCPIP
#if !defined(MG_ENABLE_DRIVER_STM32H) && !defined(MG_ENABLE_DRIVER_TM4C)
#define MG_ENABLE_DRIVER_STM32 1
#else
#define MG_ENABLE_DRIVER_STM32 0
#endif
#endif

    struct mg_tcpip_driver_imxrt1020_data
    {
        // MDC clock divider. MDC clock is derived from IPS Bus clock (ipg_clk),
        // must not exceed 2.5MHz. Configuration for clock range 2.36~2.50 MHz
        //    ipg_clk       MSCR       mdc_cr VALUE
        //    -------------------------------------
        //                                -1  <-- tell driver to guess the value
        //    25 MHz        0x04           0
        //    33 MHz        0x06           1
        //    40 MHz        0x07           2
        //    50 MHz        0x09           3
        //    66 MHz        0x0D           4  <-- value for iMXRT1020-EVK at max freq.
        int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4
    };

    struct mg_tcpip_driver_stm32_data
    {
        // MDC clock divider. MDC clock is derived from HCLK, must not exceed 2.5MHz
        //    HCLK range    DIVIDER    mdc_cr VALUE
        //    -------------------------------------
        //                                -1  <-- tell driver to guess the value
        //    60-100 MHz    HCLK/42        0
        //    100-150 MHz   HCLK/62        1
        //    20-35 MHz     HCLK/16        2
        //    35-60 MHz     HCLK/26        3
        //    150-216 MHz   HCLK/102       4  <-- value for Nucleo-F* on max speed
        //    216-310 MHz   HCLK/124       5
        //    110, 111 Reserved
        int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5
    };

    struct mg_tcpip_driver_stm32h_data
    {
        // MDC clock divider. MDC clock is derived from HCLK, must not exceed 2.5MHz
        //    HCLK range    DIVIDER    mdc_cr VALUE
        //    -------------------------------------
        //                                -1  <-- tell driver to guess the value
        //    60-100 MHz    HCLK/42        0
        //    100-150 MHz   HCLK/62        1
        //    20-35 MHz     HCLK/16        2
        //    35-60 MHz     HCLK/26        3
        //    150-250 MHz   HCLK/102       4  <-- value for Nucleo-H* on max speed driven by HSI
        //    250-300 MHz   HCLK/124       5  <-- value for Nucleo-H* on max speed driven by CSI
        //    110, 111 Reserved
        int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5
    };

    struct mg_tcpip_driver_tm4c_data
    {
        // MDC clock divider. MDC clock is derived from SYSCLK, must not exceed 2.5MHz
        //    SYSCLK range   DIVIDER   mdc_cr VALUE
        //    -------------------------------------
        //                                -1  <-- tell driver to guess the value
        //    60-100 MHz    SYSCLK/42      0
        //    100-150 MHz   SYSCLK/62      1  <-- value for EK-TM4C129* on max speed
        //    20-35 MHz     SYSCLK/16      2
        //    35-60 MHz     SYSCLK/26      3
        //    0x4-0xF Reserved
        int mdc_cr; // Valid values: -1, 0, 1, 2, 3
    };

#ifdef __cplusplus
}
#endif
#endif // MONGOOSE_H
